#include "DX11Initialiser.h"

DX11Initialiser::~DX11Initialiser() {
	delete window;
}

Optional<DX11Structure> DX11Initialiser::initDX11() {
	const Optional<DX11Structure> nullStructure = Optional<DX11Structure>::absent();
	DX11Structure *structure = new DX11Structure();
	DXGI_MODE_DESC bufferDescription = createBufferDescription();
	DXGI_SWAP_CHAIN_DESC swapChainDescription = createSwapChainDescription(bufferDescription, window);
	if (!tryCreateDeviceAndSwapChain(structure, &swapChainDescription)
		|| !tryGetBackBuffer(structure)
		|| !tryCreateRenderTargetView(structure)) {
		return nullStructure;
	}
	D3D11_TEXTURE2D_DESC depthStencilDescription = createDepthStencilDescription();
	createDepthStencilView(structure, &depthStencilDescription);
	bindViewsToOutputMergerStage(structure);
	return Optional<DX11Structure>::of(structure);
}

DXGI_MODE_DESC DX11Initialiser::createBufferDescription() {
	DXGI_MODE_DESC bufferDescription;
	bufferDescription.Width = TurnsDataModel::WINDOW_DEFAULT_WIDTH;
	bufferDescription.Height = TurnsDataModel::WINDOW_DEFAULT_HEIGHT;
	bufferDescription.RefreshRate.Numerator = vsyncFrequency.exists() ? *(vsyncFrequency.get()) : DEFAULT_VSYNC_FREQUENCY;
	bufferDescription.RefreshRate.Denominator = 1;
	bufferDescription.Format = dxgiFormat.exists() ? *(dxgiFormat.get()) : DEFAULT_DXGI_FORMAT;
	bufferDescription.ScanlineOrdering = scanlineOrdering.exists() ? *(scanlineOrdering.get()) : DEFAULT_SCANLINE_ORDERING;
	bufferDescription.Scaling = scaling.exists() ? *(scaling.get()) : DEFAULT_SCALING;
	return bufferDescription;
}

DXGI_SWAP_CHAIN_DESC DX11Initialiser::createSwapChainDescription(DXGI_MODE_DESC pBufferDescription, Window *pWindow) {
	DXGI_SWAP_CHAIN_DESC swapChainDescription;
	swapChainDescription.BufferDesc = pBufferDescription;
	if (sampling.exists()) {
		swapChainDescription.SampleDesc = *(sampling.get());
	} else {
		swapChainDescription.SampleDesc.Count = DEFAULT_SAMPLING_COUNT;
		swapChainDescription.SampleDesc.Quality = DEFAULT_SAMPLING_QUALITY;
	}
	swapChainDescription.BufferUsage = dxgiUsage.exists() ? *(dxgiUsage.get()) : DEFAULT_DXGI_USAGE;
	swapChainDescription.BufferCount = bufferCount.exists() ? *(bufferCount.get()) : DEFAULT_BUFFER_COUNT;
	swapChainDescription.OutputWindow = pWindow->getHandle();
	swapChainDescription.Windowed = pWindow->getIsWindowed();
	swapChainDescription.SwapEffect = swapEffect.exists() ? *(swapEffect.get()) : DEFAULT_SWAP_EFFECT;
	swapChainDescription.Flags = 0;
	return swapChainDescription;
}

vector<IDXGIAdapter*> DX11Initialiser::getVideoCards() {
	UINT index = 0;
	IDXGIAdapter * videoCard = nullptr;
	vector<IDXGIAdapter*> videoCards = vector<IDXGIAdapter*>();
	IDXGIFactory * factory = nullptr;

	HRESULT hResult = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)(&factory));
	if (FAILED(hResult)) {
		WindowOperations::showDefaultErrorMessage(TurnsDataModel::DX11_DXGI_FACTORY_CREATION_ERROR_MESSAGE);
		return videoCards;
	}

	while (factory->EnumAdapters(index, &videoCard) != DXGI_ERROR_NOT_FOUND) {
		// TODO: save info about video card and videoCard->EnumOutputs in the future?
		DXGI_ADAPTER_DESC adapterDescription;
		videoCard->GetDesc(&adapterDescription);

		videoCards.push_back(videoCard);
		++index;
	}
	factory->Release();
	return videoCards;
}

UINT DX11Initialiser::createDeviceFlags() {
	UINT deviceFlags = 0;
	if (isDebug) {
		deviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
	}
	if (isSingleThreaded) {
		deviceFlags |= D3D11_CREATE_DEVICE_SINGLETHREADED;
	}
	if (supportsBGRAFormats) {
		deviceFlags |= D3D11_CREATE_DEVICE_BGRA_SUPPORT;
	}
	return deviceFlags;
}

bool DX11Initialiser::tryCreateDeviceAndSwapChain(DX11Structure *pStructure, DXGI_SWAP_CHAIN_DESC *pSwapChainDescription) {
	vector<IDXGIAdapter*> videoCards = getVideoCards();
	UINT deviceFlags = createDeviceFlags();
	bool isSuccess = false;
	for (IDXGIAdapter *videoCard : videoCards) {
		IDXGISwapChain *swapChain = nullptr;
		ID3D11Device *device = nullptr;
		ID3D11DeviceContext *deviceContext = nullptr;
		HRESULT hResult = createDeviceAndSwapChain(videoCard, deviceFlags, pSwapChainDescription, &swapChain, &device, &deviceContext);

		if (SUCCEEDED(hResult)) {
			isSuccess = true;
			pStructure->setSwapChain(swapChain);
			pStructure->setDevice(device);
			pStructure->setDeviceContext(deviceContext);
			break;
		}
	}
	if (!isSuccess) {
		WindowOperations::showDefaultErrorMessage(TurnsDataModel::DX11_NO_SUPPORTED_VIDEO_CARD_MESSAGE);
		return false;
	}
	return true;
}

HRESULT DX11Initialiser::createDeviceAndSwapChain(IDXGIAdapter *pVideoCard, UINT pDeviceFlags, DXGI_SWAP_CHAIN_DESC *pSwapChainDescription,
		IDXGISwapChain **ppSwapChain, ID3D11Device **ppDevice, ID3D11DeviceContext **ppDeviceContext) {
	const int featureLevelCount = 1;
	D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_0 };
	return D3D11CreateDeviceAndSwapChain(
		pVideoCard,
		driverType.exists() ? *(driverType.get()) : DEFAULT_DRIVER_TYPE,
		softwareDriverHandle.exists() ? *(softwareDriverHandle.get()) : nullptr,
		pDeviceFlags,
		featureLevels,
		featureLevelCount,
		D3D11_SDK_VERSION,
		pSwapChainDescription,
		ppSwapChain,
		ppDevice,
		nullptr,
		ppDeviceContext
	);
}

bool DX11Initialiser::tryGetBackBuffer(DX11Structure *pStructure) {
	ID3D11Texture2D *backBuffer;
	HRESULT hResult = pStructure->getSwapChain()->GetBuffer(0, __uuidof(backBuffer), (void**)&backBuffer);
	if (FAILED(hResult)) {
		WindowOperations::showDefaultErrorMessage(TurnsDataModel::DX11_GET_BUFFER_ERROR_MESSAGE);
		return false;
	}
	pStructure->setBackBuffer(backBuffer);
	return true;
}

bool DX11Initialiser::tryCreateRenderTargetView(DX11Structure *pStructure) {
	ID3D11RenderTargetView *renderTargetView;
	HRESULT hResult = pStructure->getDevice()->CreateRenderTargetView(pStructure->getBackBuffer(), NULL, &renderTargetView);
	if (FAILED(hResult)) {
		WindowOperations::showDefaultErrorMessage(TurnsDataModel::DX11_CREATE_RENDER_TARGET_VIEW_ERROR_MESSAGE);
		return false;
	}
	pStructure->setRenderTargetView(renderTargetView);
	//pStructure->getBackBuffer()->Release(); // TODO: is this needed right away?
	return true;
}

D3D11_TEXTURE2D_DESC DX11Initialiser::createDepthStencilDescription() {
	D3D11_TEXTURE2D_DESC depthStencilDescription;
	if (depthStencilSize.exists()) {
		depthStencilDescription.Width = depthStencilSize.get()->first;
		depthStencilDescription.Height = depthStencilSize.get()->second;
	} else {
		depthStencilDescription.Width = DEFAULT_DEPTH_STENCIL_WIDTH;
		depthStencilDescription.Height = DEFAULT_DEPTH_STENCIL_HEIGHT;
	}
	depthStencilDescription.MipLevels = depthStencilMipmapLevels.exists() ? *(depthStencilMipmapLevels.get()) : DEFAULT_DEPTH_STENCIL_MIPMAP_LEVELS;
	depthStencilDescription.ArraySize = 1;
	depthStencilDescription.Format = depthStencilFormat.exists() ? *(depthStencilFormat.get()) : DEFAULT_DEPTH_STENCIL_FORMAT;
	if (depthStencilSampling.exists()) {
		depthStencilDescription.SampleDesc.Count = depthStencilSampling.get()->Count;
		depthStencilDescription.SampleDesc.Quality = depthStencilSampling.get()->Quality;
	} else {
		depthStencilDescription.SampleDesc.Count = DEFAULT_DEPTH_STENCIL_SAMPLING_COUNT;
		depthStencilDescription.SampleDesc.Quality = DEFAULT_DEPTH_STENCIL_SAMPLING_QUALITY;
	}
	depthStencilDescription.Usage = depthStencilUsage.exists() ? *(depthStencilUsage.get()) : DEFAULT_DEPTH_STENCIL_USAGE;
	depthStencilDescription.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	depthStencilDescription.CPUAccessFlags = depthStencilCPUAccessFlags.exists() ? *(depthStencilCPUAccessFlags.get()) : DEFAULT_DEPTH_STENCIL_CPU_ACCESS_FLAGS;
	depthStencilDescription.MiscFlags = depthStencilMiscFlags.exists() ? *(depthStencilMiscFlags.get()) : DEFAULT_DEPTH_STENCIL_MISC_FLAGS;
	return depthStencilDescription;
}

void DX11Initialiser::createDepthStencilView(DX11Structure *pStructure, D3D11_TEXTURE2D_DESC *pDepthStencilDescription) {
	ID3D11Texture2D *depthStencilBuffer;
	ID3D11DepthStencilView *depthStencilView;
	pStructure->getDevice()->CreateTexture2D(pDepthStencilDescription, NULL, &depthStencilBuffer);
	pStructure->getDevice()->CreateDepthStencilView(depthStencilBuffer, NULL, &depthStencilView);
	pStructure->setDepthStencilView(depthStencilView);
}

void DX11Initialiser::bindViewsToOutputMergerStage(DX11Structure *pStructure) {
	ID3D11RenderTargetView * renderTargetView = pStructure->getRenderTargetView();
	pStructure->getDeviceContext()->OMSetRenderTargets(1, &renderTargetView, pStructure->getDepthStencilView());
}

DX11Initialiser * DX11Initialiser::setVSync(int frequency) {
	vsyncFrequency = Optional<int>::of(&frequency);
	return this;
}

DX11Initialiser * DX11Initialiser::setDXGIFormat(DXGI_FORMAT pDXGIFormat) {
	dxgiFormat = Optional<DXGI_FORMAT>::of(&pDXGIFormat);
	return this;
}

DX11Initialiser * DX11Initialiser::setScanlineOrdering(DXGI_MODE_SCANLINE_ORDER pScanlineOrdering) {
	scanlineOrdering = Optional<DXGI_MODE_SCANLINE_ORDER>::of(&pScanlineOrdering);
	return this;
}

DX11Initialiser * DX11Initialiser::setScaling(DXGI_MODE_SCALING pScaling) {
	scaling = Optional<DXGI_MODE_SCALING>::of(&pScaling);
	return this;
}

DX11Initialiser * DX11Initialiser::setSampling(int count, int quality) {
	DXGI_SAMPLE_DESC * samplingDescription = new DXGI_SAMPLE_DESC;
	samplingDescription->Count = count;
	samplingDescription->Quality = quality;
	sampling = Optional<DXGI_SAMPLE_DESC>::of(samplingDescription);
	return this;
}

DX11Initialiser * DX11Initialiser::setDXGIUsage(DXGI_USAGE pDXGIUsage) {
	dxgiUsage = Optional<DXGI_USAGE>::of(&pDXGIUsage);
	return this;
}

DX11Initialiser * DX11Initialiser::setBufferCount(int pBufferCount) {
	bufferCount = Optional<int>::of(&pBufferCount);
	return this;
}

DX11Initialiser * DX11Initialiser::setSwapEffect(DXGI_SWAP_EFFECT pSwapEffect) {
	swapEffect = Optional<DXGI_SWAP_EFFECT>::of(&pSwapEffect);
	return this;
}

DX11Initialiser * DX11Initialiser::setDriverType(D3D_DRIVER_TYPE pDriverType, Optional<HMODULE> pSoftwareDriverHandle) {
	driverType = Optional<D3D_DRIVER_TYPE>::of(&pDriverType);
	softwareDriverHandle = pSoftwareDriverHandle;
	return this;
}

DX11Initialiser * DX11Initialiser::setDebugMode(bool pIsDebug) {
	isDebug = pIsDebug;
	return this;
}

DX11Initialiser * DX11Initialiser::makeSingleThreaded() {
	isSingleThreaded = true;
	return this;
}

DX11Initialiser * DX11Initialiser::makeSupportBGRAFormats() {
	supportsBGRAFormats = true;
	return this;
}

DX11Initialiser * DX11Initialiser::setDepthStencilSize(int pDepthStencilWidth, int pDepthStencilHeight) {
	pair<int, int> size = pair<int, int>(pDepthStencilWidth, pDepthStencilHeight);
	depthStencilSize = Optional<pair<int, int>>::of(&size);
	return this;
}

DX11Initialiser * DX11Initialiser::setDepthStencilMipmapLevels(int pDepthStencilMipmapLevels) {
	depthStencilMipmapLevels = Optional<int>::of(&pDepthStencilMipmapLevels);
	return this;
}

DX11Initialiser * DX11Initialiser::setDepthStencilFormat(DXGI_FORMAT pDepthStencilFormat) {
	depthStencilFormat = Optional<DXGI_FORMAT>::of(&pDepthStencilFormat);
	return this;
}

DX11Initialiser * DX11Initialiser::setDepthStencilSampling(int count, int quality) {
	DXGI_SAMPLE_DESC * samplingDescription = new DXGI_SAMPLE_DESC;
	samplingDescription->Count = count;
	samplingDescription->Quality = quality;
	depthStencilSampling = Optional<DXGI_SAMPLE_DESC>::of(samplingDescription);
	return this;
}

DX11Initialiser * DX11Initialiser::setDepthStencilUsage(D3D11_USAGE pDepthStencilUsage) {
	depthStencilUsage = Optional<D3D11_USAGE>::of(&pDepthStencilUsage);
	return this;
}

DX11Initialiser * DX11Initialiser::setDepthStencilCPUAccessFlags(int pDepthStencilCPUAccessFlags) {
	depthStencilCPUAccessFlags = Optional<int>::of(&pDepthStencilCPUAccessFlags);
	return this;
}

DX11Initialiser * DX11Initialiser::setDepthStencilMiscFlags(int pDepthStencilMiscFlags) {
	depthStencilMiscFlags = Optional<int>::of(&pDepthStencilMiscFlags);
	return this;
}
